#region history
//************************************************************************************
// 20080103/WMB/LinePositionsCache.cs created for Mitosoft.
// 
// Remarks, Restrictions
// ===================================================================================
//
//************************************************************************************
#endregion

#region using declarations

using System;
using System.Collections.Generic;

#endregion

namespace Mitosoft.TextFileReader {
  
  /// <summary>
  /// Represents a cache holding line position informations.
  /// </summary>
  public class LinePositionsCache {

    #region Fields
    
    protected uint _cacheSize = 0;
    protected List<ILinePosition> _linePositions;
    protected bool _cacheIsFull;
    protected uint _positionToBeRemovedInCache;
    
    #endregion

    #region Constructor(s)
    
    /// <summary>
    /// Creates a new instance of a LinePositionsCache object.
    /// </summary>
    public LinePositionsCache(uint cacheSize) {
      
      // Check parameters:
      if (cacheSize == 0)
        throw new ArgumentOutOfRangeException(
          "cacheSize",
          cacheSize,
          string.Format(
            "The size for the cache containing " +
            "the line positions must be greater then 0."));
      
      // Set values:
      CacheSize = cacheSize;
    }
    
    #endregion

    #region Properties

    /// <summary>
    /// Represents the position inside the cache which is marked to be removed.
    /// </summary>
    protected uint PositionToBeRemovedInCache {
      get { return _positionToBeRemovedInCache; }
      set {
        // We want always the position to cycle around the cache:
        if (value >= CacheSize)
          value = 0;
        
        // Exit if the value is already set:
        if (PositionToBeRemovedInCache == value)
          return;
        
        // Save the value:
        _positionToBeRemovedInCache = value;
      }
    }

    /// <summary>
    /// Represents if the cache is full.
    /// </summary>
    public bool CacheIsFull {
      get { return _cacheIsFull; }
      protected set {
        // Exit if the value is already set:
        if (CacheIsFull == value) return;
        
        // Save the value:
        _cacheIsFull = value;

        if (CacheIsFull)
          PositionToBeRemovedInCache = 0;
      }
    }

    /// <summary>
    /// Represents the cached line positions.
    /// </summary>
    protected List<ILinePosition> LinePositions {
      get { return _linePositions; }
      set { _linePositions = value; }
    }

    /// <summary>
    /// Represents the cache size.
    /// If the cache size is set the cache will be initialized, 
    /// so previous content in the cache will not exist anymore.
    /// </summary>
    protected uint CacheSize {
      get { return _cacheSize; }
      set {
        _cacheSize = value;
        Initialize();
      }
    }
    
    /// <summary>
    /// Gets or sets the line position identified by its index.
    /// </summary>
    /// <param name="index">Index identifying the line position.</param>
    /// <returns>Retrieved line position.</returns>
    protected ILinePosition this[uint index] {
      get {
        if (index >= LinePositions.Count) return null;

        return LinePositions[(int)index];
      }
      set {
        if (index > LinePositions.Count) 
          return;
        
        if (index == LinePositions.Count) 
        {
          ILinePosition newLinePosition = 
            new LinePosition(
              value.LineNumber,
              value.PositionInTheFile);
          
          LinePositions.Add(newLinePosition);
          return;
        }

        LinePositions[(int)index].LineNumber = 
          value.LineNumber;
        
        LinePositions[(int) index].PositionInTheFile = 
          value.PositionInTheFile;
      }
    }
    
    #endregion

    #region Methods
    
    /// <summary>
    /// Retrieves the nearest line position from the given position from the cache.
    /// </summary>
    /// <param name="lineNumber">Represents the line from where we want the 
    /// nearest line position.</param>
    /// <returns>The found line position. If nothing was found because the cache 
    /// is empty, the line with the number 0 and position 0 will be returned.</returns>
    public ILinePosition GetNearestLinePositionFromCache(ulong lineNumber)
    {
      ILinePosition linePositionResultWhenNothingFound = new LinePosition(0, 0);

      // Exit if we have no line positions:
      if (LinePositions == null || LinePositions.Count == 0)
        return linePositionResultWhenNothingFound;

      for (int i = LinePositions.Count - 1; i >= 0; i--)
        if (this[(uint)i].LineNumber <= lineNumber)
          return this[(uint)i];

      return linePositionResultWhenNothingFound;
    }

    /// <summary>
    /// Saves the given line position in the cache.
    /// </summary>
    /// <param name="linePositionToBeSaved">Line position to be saved</param>
    public void SaveNewLinePosition(ILinePosition linePositionToBeSaved)
    {
	  if (CacheIsFull) 
      {
        RemoveCacheElement(PositionToBeRemovedInCache);
        ManagePositionToBeRemoved();
        this[CacheSize - 1] = linePositionToBeSaved;
        return;
      }

      ILinePosition newLinePosition = 
        new LinePosition(
          linePositionToBeSaved.LineNumber,
          linePositionToBeSaved.PositionInTheFile);
      
      LinePositions.Add(newLinePosition);
      
      if (LinePositions.Count == CacheSize)
        CacheIsFull = true;
    }
    
    
    /// <summary>
    /// Manages the property this.PositionToBeRemovedInCache
    /// </summary>
    protected void ManagePositionToBeRemoved() {
      
      // If the position is at the begin it may be increased:
      if (PositionToBeRemovedInCache == 0) {
        PositionToBeRemovedInCache++;
        return;
      }
      
      // Otherwise (the current position is not at the begin)
      // the line number of the line position the pointer is
      // showing to must have the same difference as the
      // first line number:
      if (this[PositionToBeRemovedInCache].LineNumber -
          this[PositionToBeRemovedInCache - 1].LineNumber >=
          this[0].LineNumber)
        PositionToBeRemovedInCache++;
    }
    
    
    /// <summary>
    /// Removes the given element in the cache by shifting the higher elements.
    /// </summary>
    /// <param name="positionOfTheElementToBeRemovedInTheCache">Position of the element to be removed.</param>
    protected void RemoveCacheElement(uint positionOfTheElementToBeRemovedInTheCache) {
      
      // Check parameters and correct it if needed:
      if (positionOfTheElementToBeRemovedInTheCache >= CacheSize)
        positionOfTheElementToBeRemovedInTheCache = CacheSize - 1;

      for (uint i = positionOfTheElementToBeRemovedInTheCache + 1; 
           i <= CacheSize - 1; 
           i++)
        this[i - 1] = this[i];
    }

    /// <summary>
    /// Doing initialization jobs:
    /// Initializing the internal collection with line positions.
    /// </summary>
    protected void Initialize() {
      InitializeLinePositions();
      CacheIsFull = false;
    }
    
    /// <summary>
    /// Initializes the line positions collection.
    /// </summary>
    protected void InitializeLinePositions() {
      LinePositions = new List<ILinePosition>((int)CacheSize);
    }

    #endregion

  }
  
}
